/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.mail.pop3;

import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.event.*;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.EOFException;
import java.util.Vector;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.lang.reflect.Constructor;

import com.sun.mail.util.LineInputStream;
import com.sun.mail.util.MailLogger;

/**
 * A POP3 Folder (can only be "INBOX").
 * 
 * See the <a href="package-summary.html">com.sun.mail.pop3</a> package
 * documentation for further information on the POP3 protocol provider.
 * <p>
 * 
 * @author Bill Shannon
 * @author John Mani (ported to the javax.mail APIs)
 */
public class POP3Folder extends Folder {

	private String name;
	private POP3Store store;
	private volatile Protocol port;
	private int total;
	private int size;
	private boolean exists = false;
	private volatile boolean opened = false;
	private Vector message_cache;
	private boolean doneUidl = false;
	private volatile TempFile fileCache = null;

	MailLogger logger; // package private, for POP3Message

	POP3Folder(POP3Store store, String name) {
		super(store);
		this.name = name;
		this.store = store;
		if (name.equalsIgnoreCase("INBOX"))
			exists = true;
		logger = new MailLogger(this.getClass(), "DEBUG POP3", store.getSession());
	}

	public String getName() {
		return name;
	}

	public String getFullName() {
		return name;
	}

	public Folder getParent() {
		return new DefaultFolder(store);
	}

	/**
	 * Always true for the folder "INBOX", always false for
	 * any other name.
	 * 
	 * @return true for INBOX, false otherwise
	 */
	public boolean exists() {
		return exists;
	}

	/**
	 * Always throws <code>MessagingException</code> because no POP3 folders
	 * can contain subfolders.
	 * 
	 * @exception MessagingException
	 *                always
	 */
	public Folder[] list(String pattern) throws MessagingException {
		throw new MessagingException("not a directory");
	}

	/**
	 * Always returns a NUL character because POP3 doesn't support a hierarchy.
	 * 
	 * @return NUL
	 */
	public char getSeparator() {
		return '\0';
	}

	/**
	 * Always returns Folder.HOLDS_MESSAGES.
	 * 
	 * @return Folder.HOLDS_MESSAGES
	 */
	public int getType() {
		return HOLDS_MESSAGES;
	}

	/**
	 * Always returns <code>false</code>; the POP3 protocol doesn't
	 * support creating folders.
	 * 
	 * @return false
	 */
	public boolean create(int type) throws MessagingException {
		return false;
	}

	/**
	 * Always returns <code>false</code>; the POP3 protocol provides
	 * no way to determine when a new message arrives.
	 * 
	 * @return false
	 */
	public boolean hasNewMessages() throws MessagingException {
		return false; // no way to know
	}

	/**
	 * Always throws <code>MessagingException</code> because no POP3 folders
	 * can contain subfolders.
	 * 
	 * @exception MessagingException
	 *                always
	 */
	public Folder getFolder(String name) throws MessagingException {
		throw new MessagingException("not a directory");
	}

	/**
	 * Always throws <code>MethodNotSupportedException</code> because the POP3
	 * protocol doesn't allow the INBOX to
	 * be deleted.
	 * 
	 * @exception MethodNotSupportedException
	 *                always
	 */
	public boolean delete(boolean recurse) throws MessagingException {
		throw new MethodNotSupportedException("delete");
	}

	/**
	 * Always throws <code>MethodNotSupportedException</code> because the POP3
	 * protocol doesn't support multiple folders.
	 * 
	 * @exception MethodNotSupportedException
	 *                always
	 */
	public boolean renameTo(Folder f) throws MessagingException {
		throw new MethodNotSupportedException("renameTo");
	}

	/**
	 * Throws <code>FolderNotFoundException</code> unless this
	 * folder is named "INBOX".
	 * 
	 * @exception FolderNotFoundException
	 *                if not INBOX
	 * @exception AuthenticationException
	 *                authentication failures
	 * @exception MessagingException
	 *                other open failures
	 */
	public synchronized void open(int mode) throws MessagingException {
		checkClosed();
		if (!exists)
			throw new FolderNotFoundException(this, "folder is not INBOX");

		try {
			port = store.getPort(this);
			Status s = port.stat();
			total = s.total;
			size = s.size;
			this.mode = mode;
			if (store.useFileCache) {
				try {
					fileCache = new TempFile(store.fileCacheDir);
				} catch (IOException ex) {
					logger.log(Level.FINE, "failed to create file cache", ex);
					throw ex; // caught below
				}
			}
			opened = true;
		} catch (IOException ioex) {
			try {
				if (port != null)
					port.quit();
			} catch (IOException ioex2) {
				// ignore
			} finally {
				port = null;
				store.closePort(this);
			}
			throw new FolderOpenFailedException(this, ioex);
		}

		// Create the message cache vector of appropriate size
		message_cache = new Vector(total);
		message_cache.setSize(total);
		doneUidl = false;

		notifyConnectionListeners(ConnectionEvent.OPENED);
	}

	public synchronized void close(boolean expunge) throws MessagingException {
		checkOpen();

		try {
			/*
			 * Some POP3 servers will mark messages for deletion when
			 * they're read. To prevent such messages from being
			 * deleted before the client deletes them, you can set
			 * the mail.pop3.rsetbeforequit property to true. This
			 * causes us to issue a POP3 RSET command to clear all
			 * the "marked for deletion" flags. We can then explicitly
			 * delete messages as desired.
			 */
			if (store.rsetBeforeQuit)
				port.rset();
			POP3Message m;
			if (expunge && mode == READ_WRITE) {
				// find all messages marked deleted and issue DELE commands
				for (int i = 0; i < message_cache.size(); i++) {
					if ((m = (POP3Message) message_cache.elementAt(i)) != null) {
						if (m.isSet(Flags.Flag.DELETED))
							try {
								port.dele(i + 1);
							} catch (IOException ioex) {
								throw new MessagingException("Exception deleting messages during close", ioex);
							}
					}
				}
			}

			/*
			 * Flush and free all cached data for the messages.
			 */
			for (int i = 0; i < message_cache.size(); i++) {
				if ((m = (POP3Message) message_cache.elementAt(i)) != null)
					m.invalidate(true);
			}

			port.quit();
		} catch (IOException ex) {
			// do nothing
		} finally {
			port = null;
			store.closePort(this);
			message_cache = null;
			opened = false;
			notifyConnectionListeners(ConnectionEvent.CLOSED);
			if (fileCache != null) {
				fileCache.close();
				fileCache = null;
			}
		}
	}

	public synchronized boolean isOpen() {
		if (!opened)
			return false;
		try {
			if (!port.noop())
				throw new IOException("NOOP failed");
		} catch (IOException ioex) {
			try {
				close(false);
			} catch (MessagingException mex) {
				// ignore it
			} finally {
				return false;
			}
		}
		return true;
	}

	/**
	 * Always returns an empty <code>Flags</code> object because
	 * the POP3 protocol doesn't support any permanent flags.
	 * 
	 * @return empty Flags object
	 */
	public Flags getPermanentFlags() {
		return new Flags(); // empty flags object
	}

	/**
	 * Will not change while the folder is open because the POP3
	 * protocol doesn't support notification of new messages
	 * arriving in open folders.
	 */
	public synchronized int getMessageCount() throws MessagingException {
		if (!opened)
			return -1;
		checkReadable();
		return total;
	}

	public synchronized Message getMessage(int msgno) throws MessagingException {
		checkOpen();

		POP3Message m;

		// Assuming that msgno is <= total
		if ((m = (POP3Message) message_cache.elementAt(msgno - 1)) == null) {
			m = createMessage(this, msgno);
			message_cache.setElementAt(m, msgno - 1);
		}
		return m;
	}

	protected POP3Message createMessage(Folder f, int msgno) throws MessagingException {
		POP3Message m = null;
		Constructor cons = store.messageConstructor;
		if (cons != null) {
			try {
				Object[] o = { this, new Integer(msgno) };
				m = (POP3Message) cons.newInstance(o);
			} catch (Exception ex) {
				// ignore
			}
		}
		if (m == null)
			m = new POP3Message(this, msgno);
		return m;
	}

	/**
	 * Always throws <code>MethodNotSupportedException</code> because the POP3
	 * protocol doesn't support appending messages.
	 * 
	 * @exception MethodNotSupportedException
	 *                always
	 */
	public void appendMessages(Message[] msgs) throws MessagingException {
		throw new MethodNotSupportedException("Append not supported");
	}

	/**
	 * Always throws <code>MethodNotSupportedException</code> because the POP3
	 * protocol doesn't support expunging messages
	 * without closing the folder; call the {@link #close close} method
	 * with the <code>expunge</code> argument set to <code>true</code> instead.
	 * 
	 * @exception MethodNotSupportedException
	 *                always
	 */
	public Message[] expunge() throws MessagingException {
		throw new MethodNotSupportedException("Expunge not supported");
	}

	/**
	 * Prefetch information about POP3 messages.
	 * If the FetchProfile contains <code>UIDFolder.FetchProfileItem.UID</code>,
	 * POP3 UIDs for all messages in the folder are fetched using the POP3
	 * UIDL command.
	 * If the FetchProfile contains <code>FetchProfile.Item.ENVELOPE</code>,
	 * the headers and size of all messages are fetched using the POP3 TOP
	 * and LIST commands.
	 */
	public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
		checkReadable();
		if (!doneUidl && store.supportsUidl && fp.contains(UIDFolder.FetchProfileItem.UID)) {
			/*
			 * Since the POP3 protocol only lets us fetch the UID
			 * for a single message or for all messages, we go ahead
			 * and fetch UIDs for all messages here, ignoring the msgs
			 * parameter. We could be more intelligent and base this
			 * decision on the number of messages fetched, or the
			 * percentage of the total number of messages fetched.
			 */
			String[] uids = new String[message_cache.size()];
			try {
				if (!port.uidl(uids))
					return;
			} catch (EOFException eex) {
				close(false);
				throw new FolderClosedException(this, eex.toString());
			} catch (IOException ex) {
				throw new MessagingException("error getting UIDL", ex);
			}
			for (int i = 0; i < uids.length; i++) {
				if (uids[i] == null)
					continue;
				POP3Message m = (POP3Message) getMessage(i + 1);
				m.uid = uids[i];
			}
			doneUidl = true; // only do this once
		}
		if (fp.contains(FetchProfile.Item.ENVELOPE)) {
			for (int i = 0; i < msgs.length; i++) {
				try {
					POP3Message msg = (POP3Message) msgs[i];
					// fetch headers
					msg.getHeader("");
					// fetch message size
					msg.getSize();
				} catch (MessageRemovedException mex) {
					// should never happen, but ignore it if it does
				}
			}
		}
	}

	/**
	 * Return the unique ID string for this message, or null if
	 * not available. Uses the POP3 UIDL command.
	 * 
	 * @return unique ID string
	 * @exception MessagingException
	 */
	public synchronized String getUID(Message msg) throws MessagingException {
		checkOpen();
		POP3Message m = (POP3Message) msg;
		try {
			if (!store.supportsUidl)
				return null;
			if (m.uid == POP3Message.UNKNOWN)
				m.uid = port.uidl(m.getMessageNumber());
			return m.uid;
		} catch (EOFException eex) {
			close(false);
			throw new FolderClosedException(this, eex.toString());
		} catch (IOException ex) {
			throw new MessagingException("error getting UIDL", ex);
		}
	}

	/**
	 * Return the size of this folder, as was returned by the POP3 STAT
	 * command when this folder was opened.
	 * 
	 * @return folder size
	 * @exception IllegalStateException
	 *                if the folder isn't open
	 */
	public synchronized int getSize() throws MessagingException {
		checkOpen();
		return size;
	}

	/**
	 * Return the sizes of all messages in this folder, as returned
	 * by the POP3 LIST command. Each entry in the array corresponds
	 * to a message; entry <i>i</i> corresponds to message number <i>i+1</i>.
	 * 
	 * @return array of message sizes
	 * @exception IllegalStateException
	 *                if the folder isn't open
	 * @since JavaMail 1.3.3
	 */
	public synchronized int[] getSizes() throws MessagingException {
		checkOpen();
		int sizes[] = new int[total];
		InputStream is = null;
		LineInputStream lis = null;
		try {
			is = port.list();
			lis = new LineInputStream(is);
			String line;
			while ((line = lis.readLine()) != null) {
				try {
					StringTokenizer st = new StringTokenizer(line);
					int msgnum = Integer.parseInt(st.nextToken());
					int size = Integer.parseInt(st.nextToken());
					if (msgnum > 0 && msgnum <= total)
						sizes[msgnum - 1] = size;
				} catch (Exception e) {
				}
			}
		} catch (IOException ex) {
			// ignore it?
		} finally {
			try {
				if (lis != null)
					lis.close();
			} catch (IOException cex) {
			}
			try {
				if (is != null)
					is.close();
			} catch (IOException cex) {
			}
		}
		return sizes;
	}

	/**
	 * Return the raw results of the POP3 LIST command with no arguments.
	 * 
	 * @return InputStream containing results
	 * @exception IllegalStateException
	 *                if the folder isn't open
	 * @since JavaMail 1.3.3
	 */
	public synchronized InputStream listCommand() throws MessagingException, IOException {
		checkOpen();
		return port.list();
	}

	/**
	 * Close the folder when we're finalized.
	 */
	protected void finalize() throws Throwable {
		super.finalize();
		close(false);
	}

	/* Ensure the folder is open */
	private void checkOpen() throws IllegalStateException {
		if (!opened)
			throw new IllegalStateException("Folder is not Open");
	}

	/* Ensure the folder is not open */
	private void checkClosed() throws IllegalStateException {
		if (opened)
			throw new IllegalStateException("Folder is Open");
	}

	/* Ensure the folder is open & readable */
	private void checkReadable() throws IllegalStateException {
		if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
			throw new IllegalStateException("Folder is not Readable");
	}

	/* Ensure the folder is open & writable */
	/*
	 * private void checkWritable() throws IllegalStateException {
	 * if (!opened || mode != READ_WRITE)
	 * throw new IllegalStateException("Folder is not Writable");
	 * }
	 */

	/**
	 * Centralize access to the Protocol object by POP3Message
	 * objects so that they will fail appropriately when the folder
	 * is closed.
	 */
	Protocol getProtocol() throws MessagingException {
		Protocol p = port; // read it before close() can set it to null
		checkOpen();
		// close() might happen here
		return p;
	}

	/*
	 * Only here to make accessible to POP3Message.
	 */
	protected void notifyMessageChangedListeners(int type, Message m) {
		super.notifyMessageChangedListeners(type, m);
	}

	/**
	 * Used by POP3Message.
	 */
	TempFile getFileCache() {
		return fileCache;
	}
}
